package com.spring.nio.util;

import com.spring.nio.business.user.dto.UserDTO;
import com.spring.nio.business.user.param.UserUpdateParam;
import com.spring.nio.business.user.service.UserService;
import com.spring.nio.core.channel.context.SocketChannelContext;
import com.spring.nio.core.configuration.NioServerBootstrap;
import com.spring.nio.core.event.handler.ReadEventHandler;
import com.spring.nio.core.startup.BasicServer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.channels.*;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.Optional;

/**
 * @author: YSFY
 * @date: 2021-03-24 18:33
 * @pacage_name: com.spring.nio.util
 * @class_name: NioUtil
 * @description: Nio服务工具类
 */
@Slf4j
public class NioUtil {

    /**
     * 根据key获取配置文件数据
     * @param param
     * @return object
     * */
    public static String getValue(String param) {
        Map<String, Object> cacheMap = BasicServer.getMap();
        if(!cacheMap.isEmpty() && cacheMap.containsKey(param)) {
            return cacheMap.get(param).toString();
        }

        return "";
    }

    /**
     * 创建Selector
     * @return selector
     * */
    public static Selector openSelector() throws IOException {
        return Selector.open();
    }

    /**
     * Selector 可选事件注册
     * @param socketChannel
     * @param selector
     * @param opt
     * @param socketChannelContext
     * @return
     * @throws IOException
     * */
    public static SelectionKey eventRegister(SocketChannel socketChannel, Selector selector, final int opt, SocketChannelContext socketChannelContext) throws IOException {
        if(!selector.isOpen()) {
            log.error("[ " + LocalTime.formatDate(LocalDateTime.now()) + " ] NioUtil | --> Selector is not open yet, threadName = {}", Thread.currentThread().getName());
            return null;
        }

        socketChannel.configureBlocking(false);
        selector.wakeup();
        SelectionKey selectionKey = socketChannel.register(selector, opt);
        selectionKey.attach(socketChannelContext);
        return selectionKey;
    }

    /**
     * 根据SocketChannel获取本地连接信息(ip:port)
     * @param socketChannel
     * @return
     * */
    public static String getLocalAddress(SocketChannel socketChannel) {
        String ip = "";
        Integer port = 0;
        try {
            ip = ((InetSocketAddress) socketChannel.getLocalAddress()).getAddress().getHostAddress();
            port = ((InetSocketAddress) socketChannel.getLocalAddress()).getPort();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return (ip + ":" + port);
    }

    /**
     * 根据SocketChannel获取远程连接信息(ip:port)
     * @param socketChannel
     * @return
     * */
    public static String getRemoteAddress(SocketChannel socketChannel) {
        String ip = "";
        Integer port = 0;
        try {
            ip = ((InetSocketAddress) socketChannel.getRemoteAddress()).getAddress().getHostAddress();
            port = ((InetSocketAddress) socketChannel.getRemoteAddress()).getPort();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return (ip + ":" + port);
    }

    /**
     * 获取服务端本地地址
     * @param serverSocketChannel
     * @return
     */
    public static String getServerLocalAddress(ServerSocketChannel serverSocketChannel) {
        String ip = "";
        Integer port = 0;
        try {
            ip = ((InetSocketAddress) serverSocketChannel.getLocalAddress()).getAddress().getHostAddress();
            port = ((InetSocketAddress) serverSocketChannel.getLocalAddress()).getPort();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return (ip + ":" + port);
    }

    /**
     * 关闭socketChannel, socketChannel可能为聊天或是文件通道，存在特殊情况: 如果是下载文件通道，那么应该关闭对应的该通道文件下载任务,如果是聊天通道，则需要清空所有的文件下载任务
     * @param socketChannelContext
     * @param isClearFileUploadTask 是否只单纯关闭当前通道对应的文件上传任务
     * @param isClearFileDownloadTask 是否只单纯关闭当前通道对应的文件下载任务
     * @param isLogout 是否关闭通道的同时执行用户下线
     * @return Boolean 关闭结果
     * */
    public static Boolean closedAndRelease(SocketChannelContext socketChannelContext,Boolean isLogout, Boolean isClearFileUploadTask, Boolean isClearFileDownloadTask) {
        String remoteAddress = "";
        try {
            if(Optional.ofNullable(socketChannelContext).isPresent()) {
                remoteAddress = socketChannelContext.getRemoteAddress();
                String localAddress = socketChannelContext.getLocalAddress();

                // 1、关闭socketChannel，将会发送流截至符 -1到客户端
                Socket socket = socketChannelContext.getSocketChannel().socket();
                if(!socket.isClosed()) {
                    socketChannelContext.getSocketChannel().shutdownInput();
                    socketChannelContext.getSocketChannel().shutdownOutput();
                    socketChannelContext.getSocketChannel().close();
                }
                log.info("[ " + LocalTime.formatDate(LocalDateTime.now()) + " ] NioUtil | --> 服务端通道关闭成功, remoteAddress = {}, localAddress = {}", remoteAddress, localAddress);
            }
        } catch (Exception e) {
            if(e instanceof ClosedChannelException) {
                log.warn("[ " + LocalTime.formatDate(LocalDateTime.now()) + " ] NioUtil | --> socketChannel shutdown input or output warning, channel is closed, address = {}, message = {}", socketChannelContext.getLocalAddress(), e.getMessage());
            }

            if(e instanceof NotYetConnectedException) {
                log.warn("[ " + LocalTime.formatDate(LocalDateTime.now()) + " ] NioServerContext | --> socketChannel is not connect yet, address = {}, message = {}", socketChannelContext.getLocalAddress(), e.getMessage());
            }

            e.printStackTrace();
        } finally {
            // 1、清空当前通道缓存数据
            ReadEventHandler.channelDataMap.remove(remoteAddress);
            log.info("[ " + LocalTime.formatDate(LocalDateTime.now()) + " ] NioUtil | --> 服务端通道缓存数据已清空, remoteAddress = {}, localAddress = {}", remoteAddress);

            // 2、清除用户及上传任务及下载任务
            NioServerBootstrap.getConfig().getClassPathXmlApplicationContext().getBean(UserService.class).offlineUser(remoteAddress, isLogout, isClearFileUploadTask, isClearFileDownloadTask);
            return Boolean.TRUE;
        }
    }
}
